home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
FishMarket 1.0
/
FishMarket v1.0.iso
/
fishies
/
076-100
/
disk_095
/
journal
/
playback.c
< prev
next >
Wrap
C/C++ Source or Header
|
1992-05-06
|
19KB
|
659 lines
/*
* PLAYBACK.C - Plays back mouse and keyboard events that were recorded
* by the JOURNAL program.
*
* Copyright (c) 1987 by Davide P. Cervone
* You may use this code provided this copyright notice is kept intact.
*/
#include "journal.h"
/*
* Version number and author
*/
char *version = "Playback v1.0 (June 1987)";
char *author = "Copyright (c) 1987 by Davide P. Cervone";
/*
* Usage string
*/
#define USAGE "PLAYPACK [FROM] file [EVENTS n] [[NO]SMOOTH"
/*
* Macros to tell whether the user pressed CTRL-C
*/
#define CONTROL IEQUALIFIER_CONTROL
#define KEY_C 0x33
#define CTRL_C(e) (((e)->ie_Qualifier & CONTROL) && ((e)->ie_Code == KEY_C))
/*
* The packed code for a RAWMOUSE event with NOBUTTON pressed (i.e., one
* that probably contains more than one event compressed into a single
* entry in the file).
*/
#define MOUSEMOVE 0xE2
/*
* Macro to check whether a command-line argument matches a given string
*/
#define ARGMATCH(s) (stricmp(s,*argv) == 0)
/*
* The functions that PLAYBACK can perform
*/
#define SHOW_USAGE 0
#define READ_JOURNAL 1
#define JUST_EXIT 2
/*
* Global Variables
*/
struct MsgPort *InputPort = NULL; /* Port for the Input.Device */
struct IOStdReq *InputBlock = NULL; /* Request block for the Input.Device */
struct Task *theTask = NULL; /* pointer to the main process */
int HandlerActive = FALSE; /* TRUE when handler has been added */
LONG InputDevice = FALSE; /* TRUE when Input.Device is open */
LONG theSignal = 0; /* used when an event is freed */
LONG theMask; /* 1 << theSignal */
UWORD Ticks = 0; /* number of ticks between events */
LONG TimerMics = 0; /* last timer event's micros field */
LONG TimerSecs = 0; /* last timer event's seconds field */
struct InputEvent *Event = NULL; /* pointer to array of input events */
struct SmallEvent TinyEvent; /* a compressed event from the file */
long MaxEvents = 50; /* size of the Event array */
short Smoothing = TRUE; /* TRUE if smoothing requested */
short LastPosted = 0; /* Event index for last-posted event */
short NextToPost = 0; /* Event index for next event to post */
short NextFree = 0; /* Event index for next event to use */
FILE *InFile = NULL; /* journal file pointer */
char *JournalFile = NULL; /* name of journal file */
struct Interrupt HandlerData = /* used to add an input handler */
{
{NULL, NULL, 0, 51, NULL}, /* Node structure (nl_Pri = 51) */
NULL, /* data pointer */
&myHandlerStub /* code pointer */
};
/*
* myHandler()
*
* This is the input handler that posts the events read from the journal file.
*
* First, free any events that were posted last time myHandler was
* called by the Input.Device. Signal the main process when any are freed,
* in case it is waiting for an event to be freed.
*
* Then, look through the list of events received from the Input.Device.
* Check whether a new event is ready to be posted (i.e., one is available
* and the proper number of ticks have been counted). If so, then set its
* time fields to the proper time, add it into the event list, and look at
* the next event. Set the tick count to zero again and check the next
* event in the array.
*
* Once any new events have been added, check whether the current event
* from the Input.Device is a timer event. If so, then increment the tick
* count and record its time field. If not, then check whether it is a
* raw mouse or raw key event. If it is, then if it is a CTRL-C, signal the
* main process that the user wants to abort the playback. Remove the mouse
* or key event from the event list so that it will not interfere with the
* playback events (i.e., the keyboard and mouse are disabled while PLAYBACK
* is running).
*
* Finally, go on to the the next event in the chain and continue the loop.
* Once all the events have been processed, return the modified list
* (with new events added from the file and keyboard and mouse events removed)
* so that Intuition can act on them.
*/
struct InputEvent *myHandler(EventList,data)
struct InputEvent *EventList;
APTR data;
{
struct InputEvent **EventPtr = &EventList;
struct InputEvent *toPost = &Event[NextToPost];
while (NextToPost != LastPosted)
{
Event[LastPosted].my_InUse = FALSE;
Event[LastPosted].my_Ready = FALSE;
LastPosted = (LastPosted + 1) % MaxEvents;
Signal(theTask,theMask);
}
Forbid();
while (*EventPtr)
{
while (toPost->my_Ready && Ticks >= toPost->my_Ticks)
{
toPost->ie_Secs = TimerSecs;
toPost->ie_Mics += TimerMics;
if (toPost->ie_Mics > MILLION)
{
toPost->ie_Secs++;
toPost->ie_Mics -= MILLION;
}
toPost->ie_NextEvent = *EventPtr;
*EventPtr = toPost;
EventPtr = &(toPost->ie_NextEvent);
NextToPost = (NextToPost + 1) % MaxEvents;
toPost = &Event[NextToPost];
Ticks = 0;
}
if ((*EventPtr)->ie_Class == IECLASS_TIMER)
{
Ticks++;
TimerSecs = (*EventPtr)->ie_Secs;
TimerMics = (*EventPtr)->ie_Mics;
} else {
if ((*EventPtr)->ie_Class == IECLASS_RAWMOUSE ||
(*EventPtr)->ie_Class == IECLASS_RAWKEY)
{
if (CTRL_C(*EventPtr)) Signal(theTask,SIGBREAKF_CTRL_C);
*EventPtr = (*EventPtr)->ie_NextEvent;
}
}
EventPtr = &((*EventPtr)->ie_NextEvent);
}
Permit();
return(EventList);
}
/*
* Ctrl_C()
*
* Dummy routine to disable Lattice-C CTRL-C trapping.
*/
#ifndef MANX
int Ctrl_C()
{
return(0);
}
#endif
/*
* DoExit()
*
* General purpose exit routine. If 's' is not NULL, then print an
* error message with up to three parameters. Remove the handler (if
* it is active), free any memory, close any open files, delete any ports,
* free any used signals, etc.
*/
void DoExit(s,x1,x2,x3)
char *s, *x1, *x2, *x3;
{
long status = 0;
if (s != NULL)
{
printf(s,x1,x2,x3);
printf("\n");
status = RETURN_ERROR;
}
if (HandlerActive) RemoveHandler();
if (Event) FreeMem(Event,IE_SIZE * MaxEvents);
if (InFile) fclose(InFile);
if (InputDevice) CloseDevice(InputBlock);
if (InputBlock) DeleteStdIO(InputBlock);
if (InputPort) DeletePort(InputPort);
if (theSignal) FreeSignal(theSignal);
exit(status);
}
/*
* ParseArguements()
*
* Check that all the command-line arguments are valid and set the
* proper variables as requested by the user. If no keyword is specified,
* assume "FROM". If no file is specified, then show the usage. EVENTS
* regulates the size of the Event array used for buffering event
* communication between the main process and the handler. SMOOTH and
* NOSMOOTH regulate the interpolation of mouse movements between recorded
* events. The default is SMOOTH.
*/
int ParseArguments(argc,argv)
int argc;
char **argv;
{
int function = READ_JOURNAL;
while (--argc > 0)
{
argv++;
if (argc > 1 && ARGMATCH("FROM"))
{
JournalFile = *(++argv);
argc--;
}
else if (argc > 1 && ARGMATCH("EVENTS"))
{
argc--;
if (sscanf(*(++argv),"%ld",&MaxEvents) != 1)
{
printf("Event count must be numeric: '%s'\n",*argv);
function = JUST_EXIT;
}
if (MaxEvents <= 1)
{
printf("Event count must be greater than 1: '%d'\n",MaxEvents);
function = JUST_EXIT;
}
}
else if (ARGMATCH("NOSMOOTH")) Smoothing = FALSE;
else if (ARGMATCH("SMOOTH")) Smoothing = TRUE;
else if (JournalFile == NULL) JournalFile = *argv;
else function = SHOW_USAGE;
}
if (JournalFile == NULL && function == READ_JOURNAL) function = SHOW_USAGE;
return(function);
}
/*
* OpenJournal()
*
* Open the journal file and check for errors. Read the version
* information to the file (someday we may need to check this).
*/
void OpenJournal()
{
char fileversion[32];
InFile = fopen(JournalFile,"r");
if (InFile == NULL)
DoExit("Can't Open Journal File '%s', error %ld",JournalFile,_OSERR);
if (fread(fileversion,sizeof(fileversion),1,InFile) != 1)
DoExit("Can't read version from '%s', error %ld",JournalFile,_OSERR);
}
/*
* GetEventMemory()
*
* Allocate memory for the Event array (of size MaxEvents, specified by
* the EVENT option).
*/
void GetEventMemory()
{
Event = AllocMem(IE_SIZE * MaxEvents, MEMF_CLEAR);
if (Event == NULL) DoExit("Can't get memory for %d Events",MaxEvents);
}
/*
* GetSignal()
*
* Allocate a signal (error if none available) and set the mask to
* the proper value.
*/
void GetSignal(theSignal,theMask)
LONG *theSignal, *theMask;
{
LONG signal;
if ((signal = AllocSignal(-ONE)) == -ONE) DoExit("Can't Allocate Signal");
*theSignal = signal;
*theMask = (ONE << signal);
}
/*
* SetupTask();
*
* Find the task pointer for the main task (so the input handler can
* signal it). Clear the CTRL signal flags (so we don't get any left
* over from before PLAYBACK was run) and allocate a signal for
* when the handler frees an event.
*/
void SetupTask()
{
theTask = FindTask(NULL);
SetSignal(0L,SIGBREAKF_ANY);
GetSignal(&theSignal,&theMask);
#ifndef MANX
onbreak(&Ctrl_C);
#endif
}
/*
* AddHandler()
*
* Add the input handler to the Input.Device handler chain. Since the
* priority is 51 it will appear BEFORE intuition, so when we insert
* new events into the chain, Intuition will process them just as though
* they came from the Input.Device.
*/
void AddHandler()
{
long status;
if ((InputPort = CreatePort(0,0)) == NULL)
DoExit("Can't Create Port");
if ((InputBlock = CreateStdIO(InputPort)) == NULL)
DoExit("Can't Create Standard IO Block");
InputDevice = (OpenDevice("input.device",0,InputBlock,0) == 0);
if (InputDevice == 0) DoExit("Can't Open Input.Device");
InputBlock->io_Command = IND_ADDHANDLER;
InputBlock->io_Data = (APTR) &HandlerData;
if (status = DoIO(InputBlock)) DoExit("Error from DoIO: %ld",status);
printf("%s - Press CTRL-C to Cancel\n",version);
HandlerActive = TRUE;
}
/*
* RemoveHandler()
*
* Remove the input handler from the Input.Device handler chain.
*/
void RemoveHandler()
{
long status;
if (HandlerActive && InputDevice && InputBlock)
{
HandlerActive = FALSE;
InputBlock->io_Command = IND_REMHANDLER;
InputBlock->io_Data = (APTR) &HandlerData;
if (status = DoIO(InputBlock)) DoExit("Error from DoIO: %ld",status);
}
printf("Playback Complete\n");
}
/*
* Create an event that moves the pointer to the upper, left-hand corner
* of the screen so that the pointer is at a known position. This is a
* large relative move (-1000,-1000).
*/
void PointerToHome()
{
struct InputEvent *theEvent = &(Event[0]);
theEvent->ie_Class = IECLASS_RAWMOUSE;
theEvent->ie_Code = IECODE_NOBUTTON;
theEvent->ie_Qualifier = IEQUALIFIER_RELATIVEMOUSE;
theEvent->ie_X = -1000;
theEvent->ie_Y = -1000;
theEvent->my_Ticks = 0;
theEvent->my_Time = 0;
theEvent->my_Ready = READY;
}
/*
* CheckForCTRLC()
*
* Read the current task signals (without changing them) and check whether
* a CTRL-C has been signalled. If so, abort the playback.
*/
void CheckForCTRLC()
{
LONG signals = SetSignal(0,0);
if (signals & SIGBREAKF_CTRL_C) DoExit("Playback Aborted");
}
/*
* GetNextFree()
*
* Set NextFree to point to the next free event in the Event array.
* If there are no free events, Wait() for the handler to signal that it
* has freed one (or for CTRL-C to be pressed).
*/
void GetNextFree()
{
LONG signals;
NextFree = (NextFree + 1) % MaxEvents;
while (Event[NextFree].my_InUse)
{
signals = Wait(theMask | SIGBREAKF_CTRL_C);
if (signals & SIGBREAKF_CTRL_C) DoExit("Playback Aborted");
}
}
#define ABS(x) (((x)<0)?-(x):(x))
/*
* MovePointer()
*
* Interpolate mouse move events that were compressed into one record in
* the journal file. The se_Count field holds the number of events that
* were compressed into one.
*
* First, unpack the X and Y movements. Record their directions in dx and dy
* and their magnitudes in abs_x and abs_y. Reduce 'count' if there would
* be events with offset of (0,0). 'x_move' specifies the x-offset for each
* event and 'x_add' specifies the fraction of a pixel correction that must
* be made (x_add/count is the fraction). 'x_count' counts the fraction of
* a pixel that has been added so far (when x_count/count >= 1 (i.e., when
* x_count >= count) we add another pixel to the x-offset). Similarly for
* the y and t variables (t is for ticks). Starting the counts at 'count/2'
* makes for smoother movement.
*
* Once these are set up, we create new mouse move events with the proper
* offsets, adding up the fractions of pixels and adding in addional
* movements whenever the fractions add up to a whole pixel (or tick).
* When a new event is set up, we mark it as READY so that the handler will
* see it and post it.
*
* Once we have sent all the events, the mouse should be in the proper
* position, so we set the tick count and XY-offset fields to 0.
*/
void MovePointer()
{
WORD abs_x,abs_y, x_count,y_count, dx,dy, x_add,y_add, x_move,y_move;
WORD t_count, t_add, t_move;
WORD x = TinyEvent.se_XY & 0xFFF;
WORD y = (TinyEvent.se_XY >> 12) & 0xFFF;
WORD i, count = TinyEvent.se_Count & COUNTMASK;
LONG Time = TinyEvent.se_Micros & 0xFFFFF;
LONG Ticks = TinyEvent.se_Ticks >> 20;
struct InputEvent *NewEvent;
x_count = y_count = t_count = 0;
if (x & 0x800) x |= 0xF000;
if (x < 0) dx = -1; else dx = 1;
if (y & 0x800) y |= 0xF000;
if (y < 0) dy = -1; else dy = 1;
abs_x = ABS(x); abs_y = ABS(y);
if (abs_x > abs_y)
{
if (count > abs_x) count = abs_x;
} else {
if (count > abs_y) count = abs_y;
}
if (count)
{
x_move = x / count; y_move = y / count; t_move = Ticks / count;
x_add = abs_x % count; y_add = abs_y % count; t_add = Ticks % count;
} else {
x_move = x; y_move = y; t_move = Ticks;
x_add = y_add = t_add = -1; count = 1;
}
x_count = y_count = t_count = count / 2;
for (i = count; i > 0; i--)
{
GetNextFree();
NewEvent = &Event[NextFree];
NewEvent->ie_Class = IECLASS_RAWMOUSE;
NewEvent->ie_Code = IECODE_NOBUTTON;
NewEvent->ie_Qualifier = TinyEvent.se_Qualifier;
NewEvent->ie_X = x_move;
NewEvent->ie_Y = y_move;
NewEvent->my_Ticks = t_move;
NewEvent->my_Time = Time;
if ((x_count += x_add) >= count)
{
x_count -= count;
NewEvent->ie_X += dx;
}
if ((y_count += y_add) >= count)
{
y_count -= count;
NewEvent->ie_Y += dy;
}
if ((t_count += t_add) > count)
{
t_count -= count;
NewEvent->my_Ticks++;
}
NewEvent->my_Ready = READY;
}
TinyEvent.se_XY &= 0xFF000000;
TinyEvent.se_Ticks &= 0xFFFFF;
}
/*
* PostNextEvent()
*
* Read an event from the journal file. If we are smoothing and the
* event is a mouse movement, them interpolate the compressed mouse
* movements. Get the next event in the Event array and unpack the
* proper values from the TinyEvent read from the file. Mark the finished
* event as READY so the handler will see it and post it.
*/
void PostNextEvent()
{
struct InputEvent *NewEvent = NULL;
if (fread((char *)&TinyEvent,sizeof(TinyEvent),1,InFile) == 1)
{
if (Smoothing && TinyEvent.se_Type == MOUSEMOVE) MovePointer();
GetNextFree();
NewEvent = &Event[NextFree];
NewEvent->ie_Class = TinyEvent.se_Type & 0x1F;
NewEvent->ie_Qualifier = TinyEvent.se_Qualifier;
NewEvent->my_Ticks = TinyEvent.se_Ticks >> 20;
NewEvent->my_Time = TinyEvent.se_Micros & 0xFFFFF;
switch(NewEvent->ie_Class)
{
case IECLASS_RAWKEY:
NewEvent->ie_Code = TinyEvent.se_Code;
NewEvent->ie_X = NewEvent->ie_Y = TinyEvent.se_Prev;
break;
case IECLASS_RAWMOUSE:
NewEvent->ie_Code = (TinyEvent.se_Type >> 5) & 0x03;
if (NewEvent->ie_Code == 0x03)
NewEvent->ie_Code = IECODE_NOBUTTON;
else
NewEvent->ie_Code |= (TinyEvent.se_Type & IECODE_UP_PREFIX) |
(IECODE_LBUTTON & ~(0x03 | IECODE_UP_PREFIX));
NewEvent->ie_X = TinyEvent.se_XY & 0xFFF;
NewEvent->ie_Y = (TinyEvent.se_XY >> 12) & 0xFFF;
NewEvent->my_Ticks = TinyEvent.se_Ticks >> 20;
if (NewEvent->ie_X & 0x800) NewEvent->ie_X |= 0xF000;
if (NewEvent->ie_Y & 0x800) NewEvent->ie_Y |= 0xF000;
break;
default:
printf("[ Unknown Event Class: %02X]\n",NewEvent->ie_Class);
break;
}
NewEvent->my_Ready = READY;
}
}
/*
* WaitForEvents()
*
* Wait for the handler to finish posting all the events in the Event
* array (so we don't remove the handler before it is done).
*/
void WaitForEvents()
{
short LastFree = NextFree;
do GetNextFree(); while (NextFree != LastFree);
}
/*
* PlayJournal()
*
* Open the journal file, set up the task and signals, and allocate the
* Event array. Add the input handler and send the pointer to the upper,
* left-hand corner of the screen. While there are still events in the
* journal file, check whether the user wants to cancel the playback and
* if not, post the next event in the file. When the end-of-file is reached
* wait for the handler to finish posting all the events in the array, and
* then remove the handler.
*/
void PlayJournal()
{
OpenJournal();
SetupTask();
GetEventMemory();
AddHandler(&myHandler);
PointerToHome();
while (feof(InFile) == 0)
{
CheckForCTRLC();
PostNextEvent();
}
WaitForEvents();
RemoveHandler();
}
/*
* main()
*
* Parse the command-line arguments, and perform the proper function
* (either show the usage, read a journal, or fall through and exit).
*/
void main(argc,argv)
int argc;
char **argv;
{
switch(ParseArguments(argc,argv))
{
case SHOW_USAGE:
printf("Usage: %s\n",USAGE);
break;
case READ_JOURNAL:
PlayJournal();
break;
}
DoExit(NULL);
}